package org.reliablesource.jdt.actions;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IParent;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.Comment;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.ui.IActionDelegate;
import org.eclipse.ui.IObjectActionDelegate;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.reliablesource.client.ui.viewers.IReliableViewer;
import org.reliablesource.client.ui.workbench.views.IReliableViewPart;
import org.reliablesource.jdt.linecounter.ReliableJDTPlugin;
import org.reliablesource.jdt.model.Contributor;
import org.reliablesource.jdt.model.IContributor;
import org.reliablesource.jdt.model.ISourceElementInfo;
import org.reliablesource.jdt.model.JavaElementAdapter;
import org.reliablesource.jdt.model.SourceElementInfo;
import org.reliablesource.jdt.viewers.ContributorViewer;
import org.reliablesource.jdt.views.ContributionView;
import org.reliablesource.jdt.views.JavaElementInfoView;
import org.reliablesource.model.core.IReliableElement;

/**
 * 
 * CountLinesAction.java<br>
 * Copyright (c) 2007, Reliable Source, Inc. All Rights Reserved<br>
 * <br>
 * 
 * Created on: Apr 17, 2007 <br>
 * <br>
 * 
 * @author Dennis Park 
 * 		<a href="mailto:dennis.park@gmail.com">dennis.park@gmail.com</a>
 * 
 */
public class CountLinesAction implements IObjectActionDelegate {

	protected IStructuredSelection currentSelection = StructuredSelection.EMPTY;

	protected IWorkbenchPart currentPart;

	public static int FAILED_TO_OPEN_RESOURCE = 1000;
	public static int FAILED_TO_READ_RESOURCE = 2000;
	
	final public static String LINE_SEPERATOR = Messages.Metrics_0;

	public static String NEW_LINE = System.getProperty(LINE_SEPERATOR);

	final public static String EMPTY_STRING = ""; //$NON-NLS-1$
	
	final public static String NO_AUTHOR = Messages.Metrics_2;

	final public static String AUTHOR = "@author"; //$NON-NLS-1$

	protected List<ISourceElementInfo> javaEltInfoList;
	
	final static protected String VIEWS[] = 
		{JavaElementInfoView.ID, ContributionView.ID};
	

	
	/**
	 * Constructor
	 */
	public CountLinesAction() {
		super();
	}

	/**
	 * @see IObjectActionDelegate#setActivePart(IAction, IWorkbenchPart)
	 */
	public void setActivePart(final IAction _action, final IWorkbenchPart _targetPart) {
		currentPart = _targetPart;
	}

	/**
	 * @see IActionDelegate#run(IAction)
	 */
	public void run(final IAction _action) {
		javaEltInfoList = new ArrayList<ISourceElementInfo>();
		ReliableJDTPlugin.contributorsMap.clear();
		try {
			ProgressMonitorDialog dlg = new ProgressMonitorDialog(null);
			dlg.setOpenOnRun(true);
			dlg.setBlockOnOpen(false);
			dlg.run(true,true, new ReliableProcess());
				
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		IWorkbenchPage _pg = PlatformUI.getWorkbench()
				.getActiveWorkbenchWindow().getActivePage();

		for (int i = 0; i < VIEWS.length; i++) {
			try {
				IReliableViewPart _part = (IReliableViewPart) _pg.showView(VIEWS[i]);
				if (_part != null) {
					IReliableViewer _viewer = _part.getViewer();
					if (_viewer != null) {
						if(_viewer instanceof ContributorViewer)
							_viewer.setInput(ReliableJDTPlugin.contributorsMap.values());
						else
							_viewer.setInput(javaEltInfoList);
						
						_viewer.refresh();
					}

				}
			} catch (final PartInitException _pie) {
				String _title = Messages.Metrics_7;
				String _message = Messages.Metrics_8
						+ Messages.Metrics_9 + JavaElementInfoView.ID;
				Status _status = new Status(Status.ERROR, ReliableJDTPlugin
						.getDefault().getBundle().getSymbolicName(), _pie
						.getStatus().getCode(), _message, _pie);
				ReliableJDTPlugin.getDefault().getLog().log(_status);
				ErrorDialog.openError(currentPart.getSite().getShell(), _title,
						_message, _status);
			}
		}
	}

	/**
	 * @see IActionDelegate#selectionChanged(IAction, ISelection)
	 */
	public void selectionChanged(final IAction _action, ISelection _selection) {
		if (_selection == null)
			_selection = StructuredSelection.EMPTY;
		if (_selection.equals(currentSelection))
			return;

		currentSelection = (IStructuredSelection) _selection;
	}
	
	class ReliableProcess implements IRunnableWithProgress {

		protected IProgressMonitor monitor;
		protected JavaElementAdapter projectAdapter = null;
		protected JavaElementAdapter packageAdapter = null;
		protected ASTParser parser;
		
		public void run(final IProgressMonitor _monitor)
				throws InvocationTargetException, InterruptedException {
			monitor = _monitor;
			
			int nSteps = currentSelection.toList().size();
			monitor.beginTask("Traversing element hierarchy..", 550);
			Iterator _it = currentSelection.iterator();
			while (_it.hasNext()) {
				Object _selectionElement = _it.next();
				try {
					traverseElement(_selectionElement);
				} catch (final JavaModelException _jme) {
					String _title = Messages.Metrics_4;
					String _message = Messages.Metrics_5 + Messages.Metrics_6;
					Status _status = new Status(Status.ERROR, ReliableJDTPlugin
							.getDefault().getBundle().getSymbolicName(),
							FAILED_TO_READ_RESOURCE, _message, _jme);
					ReliableJDTPlugin.getDefault().getLog().log(_status);
				}
			}
		}

		/**
		 * 
		 * @param _selectionElement
		 * @throws JavaModelException
		 */
		protected void traverseElement(final Object _selectionElement)
				throws JavaModelException {

			if (_selectionElement instanceof ICompilationUnit){
				
				//	Add the package to the project				
				ISourceElementInfo _elt = parseSource((ICompilationUnit) _selectionElement);
				
				javaEltInfoList.add(_elt);
				return;
			}
			else if (_selectionElement instanceof IParent) {

				if(_selectionElement instanceof IPackageFragmentRoot)
					if(((IPackageFragmentRoot)_selectionElement).isArchive())
						return;
				
				if(_selectionElement instanceof IJavaProject)
						projectAdapter  = new JavaElementAdapter((IJavaProject)_selectionElement);						
				else if (_selectionElement instanceof IPackageFragment)
						packageAdapter  = new JavaElementAdapter((IJavaElement)_selectionElement);
				
				IJavaElement[] _childElts = ((IParent) _selectionElement).getChildren();
				for (int i = 0; i < _childElts.length; i++) {
					if (_selectionElement instanceof ICompilationUnit)
						traverseElement(_childElts[i]);
					else if(_selectionElement instanceof IPackageFragmentRoot)
						traverseElement(_childElts[i]);
					else if (_selectionElement instanceof IPackageFragment)
						traverseElement(_childElts[i]);
					else if(_selectionElement instanceof IJavaProject)
						traverseElement(_childElts[i]);					
					else
						return;
				}
				
			}
		}
		

		protected ISourceElementInfo parseSource(final ICompilationUnit _compilationUnit) throws JavaModelException{			
			String _javaSrc = EMPTY_STRING;
			String _name = EMPTY_STRING;
			_javaSrc = _compilationUnit.getSource();
			_name = _compilationUnit.getElementName();
			
			int _nLOC = 0;
			
			// Calculate total lines.
			Pattern _linesInFile = Pattern.compile("^", Pattern.MULTILINE); //$NON-NLS-1$
			Matcher _lineMatcher = _linesInFile.matcher(_javaSrc);
			monitor.subTask("Calculating total lines..");
			while (_lineMatcher.find()){
				_nLOC++;
				
			}
			monitor.worked(70);
			
			//	create a new Source Element
			ISourceElementInfo _javaElement = new SourceElementInfo(_name, _nLOC);
			long _modStamp = ((IFile)_compilationUnit.getCorrespondingResource()).getLocalTimeStamp();
			if(IResource.NULL_STAMP != _modStamp){
//				long current = Calendar.getInstance().getTimeInMillis();
//				
//				long previous = current - _modStamp;
//				Calendar.getInstance().setTimeInMillis(previous);
				
				Date _date  = new Date(_modStamp);
				_date.setTime(_modStamp);
				_javaElement.setModified(_date.toString());
				_javaElement.setJavaElement(_compilationUnit);
			}
			
			if(null == parser)
				parser = ASTParser.newParser(AST.JLS3);
			
			parser.setSource(_compilationUnit);
			CompilationUnit _node = (CompilationUnit) parser.createAST(null);
			parser.setProject(_compilationUnit.getJavaProject());
			parser.createBindings(_compilationUnit.getChildren(), null);
			
			ReliableJDTCompilationUnitVisitor _visitor = new ReliableJDTCompilationUnitVisitor(_compilationUnit);
			_node.accept(_visitor);
			
			//	CU's method list
			ISourceElementInfo [] _import = _visitor.getImportList();
			for(int i = 0; i < _import.length; i++){
				_javaElement.addChild(_import[i]);
			}
			
			//	CU's method list
			ISourceElementInfo [] _fields = _visitor.getFieldList();
			for(int i = 0; i < _fields.length; i++){
				_javaElement.addChild(_fields[i]);
			}
			
			//	CU's method list
			ISourceElementInfo [] _methods = _visitor.getMethodList();
			for(int i = 0; i < _methods.length; i++){
				_javaElement.addChild(_methods[i]);
			}
			
			String _contributorName = EMPTY_STRING;
			List _comments = _node.getCommentList();
			
			IContributor _contributor;
			if (null != _comments) {
				int nComments = _comments.size();
				
				for (int i = 0; i < nComments; i++) {
					
					//	extract all contributor handles from comment blocks..
					List _e = extractAuthorsFromComment((Comment) _comments.get(i));					
					//	for each identified contributor, add contributor to map.
					for (int j = 0; j < _e.size(); j++) {
						_contributorName = _e.get(j).toString().trim();
						_contributor = ReliableJDTPlugin.contributorsMap.get(_contributorName);											
						
						projectAdapter  = new JavaElementAdapter(_compilationUnit.getJavaProject());
						
						
						if(_contributor == null){
							_contributor = new Contributor(_contributorName);
							_contributor.addChild(projectAdapter);
							ReliableJDTPlugin.contributorsMap.put(_contributorName, _contributor);
						}
						
						else{
							projectAdapter = null;
							// search in contributors' project list, create new binding.
							IReliableElement [] _projects = _contributor.getChildren();
								for(int _p = 0; _p < _projects.length; _p++){
									if(!_projects[_p].getName().equals(_compilationUnit.getJavaProject().getElementName()))
										continue;
									else{
										projectAdapter = (JavaElementAdapter) _projects[_p];									
										break;
									}
								}
								
								if(projectAdapter == null){
									projectAdapter  = new JavaElementAdapter(_compilationUnit.getJavaProject());
									_contributor.addChild(projectAdapter);
								}
								
						}
						
						
						IPackageFragment [] _mdlPkgs = _compilationUnit.getJavaProject().getPackageFragments();
						int _nPkgsOnJavaModel = _mdlPkgs.length;
						for(int q = 0; q < _nPkgsOnJavaModel; q++){
							if( _mdlPkgs[q].getElementName().equals(_compilationUnit.getParent().getElementName() )){
									IReliableElement [] _pkgObjects = projectAdapter.getChildren();
									for(int u = 0; u < _pkgObjects.length; u++){
										if(!((IPackageFragment)(((JavaElementAdapter)_pkgObjects[u]).getAdaptedObject())).getElementName().equals(_mdlPkgs[q].getElementName())){
											continue;
										}
										else{
											packageAdapter  = (JavaElementAdapter) _pkgObjects[u];
											packageAdapter.addChild(_javaElement);
											projectAdapter.addChild(packageAdapter);
										}
									}
										packageAdapter  = new JavaElementAdapter(_compilationUnit.getParent());
										packageAdapter.addChild(_javaElement);
										projectAdapter.addChild(packageAdapter);
							}
						}
					}
				}
				
			}			
			return _javaElement;
		}

		
		/**
		 * Method to extract String segment literal that is prefixed by author tag.
		 * @param _comment
		 * @return List of String literals
		 */
		public List extractAuthorsFromComment(final Comment _comment){
			
			String _javaSrc = _comment.toString();
			String _authorRegEx =AUTHOR+"\\s+((\\w+)*(\\s?)(\\w+)*)"; //$NON-NLS-1$
			Pattern _authorPattern = Pattern.compile(_authorRegEx, Pattern.MULTILINE);
			Matcher  _authorMatcher = _authorPattern.matcher(_javaSrc);
			List<String> _authorList = new LinkedList<String>();
			while (_authorMatcher.find()){
				String _name = NO_AUTHOR;
				_name = _authorMatcher.group(0).trim().substring(AUTHOR.length()).trim();
				if(!_authorList.contains(_name)){
					_authorList.add(_name);
				}
			}
			return _authorList;
		}
	}
}